#!/usr/bin/env python

'''
zbdump - a tcpdump-like tool for ZigBee/IEEE 802.15.4 networks

Compatible with Wireshark 1.1.2 and later (jwright@willhackforsushi.com)
The -p flag adds CACE PPI headers to the PCAP (ryan@rmspeers.com)
'''

import sys
import signal
import argparse
import os

from scapy.all import *
from killerbee import *
from killerbee.scapy_extensions import *

def interrupt(signum, frame):
    global packetcount
    global kb
    global pd, dt
    kb.sniffer_off()
    kb.close()
    if pd:
        pd.close()
    if dt:
        dt.close()
    print("\n{0} packets captured".format(packetcount))
    sys.exit(0)

# PcapDumper, only used if -w is specified
pd = None
# DainTreeDumper, only used if -W is specified
dt = None

# Global
packetcount = 0

# Command-line arguments
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('-i', '--iface', '--dev', action='store', dest='devstring')
#parser.add_argument('-g', '--gps', '--ignore', action='append', dest='ignore')
parser.add_argument('-w', '--pcapfile', action='store')
parser.add_argument('-W', '--dsnafile', action='store')
parser.add_argument('-p', '--ppi', action='store_true')
parser.add_argument('-P', '--pan_id_hex', action='store', default=None)
parser.add_argument('-c', '-f', '--channel', action='store', type=int, default=None)
parser.add_argument('-s', '--subghz_page', action='store', type=int, default=0)
parser.add_argument('-n', '--count', action='store', type=int, default=-1)
parser.add_argument('-D', action='store_true', dest='showdev')
parser.add_argument('-v', action='store_true', dest='verbose')
args = parser.parse_args()

if args.showdev:
    show_dev()
    sys.exit(0)

if args.verbose:
    unbuffered = os.fdopen(sys.stdout.fileno(), 'w', 0)

if args.channel == None:
    print >>sys.stderr, "ERROR: Must specify a channel."
    sys.exit(1)

if args.pcapfile is None and args.dsnafile is None:
    print >>sys.stderr, "ERROR: Must specify a savefile with -w (libpcap) or -W (Daintree SNA)"
    sys.exit(1)
elif args.pcapfile is not None:
    pd = PcapDumper(DLT_IEEE802_15_4, args.pcapfile, ppi=args.ppi)
elif args.dsnafile is not None:
    dt = DainTreeDumper(args.dsnafile)

if args.pan_id_hex:
    panid = int(args.pan_id_hex, 16)
else:
    panid = 0

kb = KillerBee(device=args.devstring)
signal.signal(signal.SIGINT, interrupt)
if not kb.is_valid_channel(args.channel, args.subghz_page):
    print >>sys.stderr, "ERROR: Must specify a valid IEEE 802.15.4 channel for the selected device."
    kb.close()
    sys.exit(1)
kb.set_channel(args.channel, args.subghz_page)
kb.sniffer_on()

#rf_freq_mhz = (args.channel - 10) * 5 + 2400
rf_freq_mhz = kb.frequency(args.channel, args.subghz_page) / 1000.0
print("zbdump: listening on \'{0}\', channel {1}, page {2} ({3} MHz), link-type DLT_IEEE802_15_4, capture size 127 bytes".format(kb.get_dev_info()[0], args.channel, args.subghz_page, rf_freq_mhz))

while args.count != packetcount:
    packet = kb.pnext()
    # packet[1] is True if CRC is correct, check removed to have promiscous capture regardless of CRC
    # if PAN filter active, only process correct PAN or ACK
    if packet and panid:
        pan, layer = kbgetpanid(Dot15d4FCS(packet['bytes']))
    if packet != None and (not panid or panid == pan): # and packet[1]:
        packetcount+=1
        if args.verbose:
            unbuffered.write('.')
        if pd:
            pd.pcap_dump(packet['bytes'], ant_dbm=packet['dbm'], freq_mhz=rf_freq_mhz)
        if dt:
            dt.pwrite(packet['bytes'])

kb.sniffer_off()
kb.close()
if pd:
    pd.close()
if dt:
    dt.close()

print("{0} packets captured".format(packetcount))

